package genetic;

import java.util.ArrayList;
import java.util.Date;
import java.util.Random;

/**
 *
 * @author shanki
 */
public abstract class GeneticAlgorithm<T extends Chromosome<T,C> ,C> {
    
    private Random random           = new Random(new Date().getTime());
    
    private int populationSize;
    private int maxTime;
    private double pRepro;
    private double pMutate;
    
    public GeneticAlgorithm(int populationSize, int maxTime, double pRepro, double pMutate) {
        if (populationSize <= 0) throw new IllegalArgumentException("populationSize must be positive and non negative");
        if (maxTime < 0) throw new IllegalArgumentException("maxTime must be posivite");
        if (pRepro < 0) throw new IllegalArgumentException("pRepro must be positive");
        if (pMutate < 0) throw new IllegalArgumentException("pMutate must be positive");
        
        this.populationSize = populationSize;
        this.maxTime        = maxTime;
        this.pRepro         = pRepro;
        this.pMutate        = pMutate;
    }
    
    protected abstract ArrayList<T> generateChromosomes(int count);
    protected abstract C getContext();
    
    public T compute() {
        ArrayList<T> population = generateChromosomes(populationSize);        
        int time                = 0;
        
        for (T chromosome : population) {
            chromosome.evaluate(getContext());
        }
        
        T best  = getBest(population);
        
        while (time < maxTime) {
            ArrayList<T> newPopulation  = new ArrayList<T>();
            Roulette<T,C> roulette      = new Roulette<T,C>(population);
            
            while(newPopulation.size() < populationSize) {
                T ch1   = roulette.select();
                T ch2   = roulette.select();
                
                T newCh1;
                T newCh2;
                
                if (random.nextDouble() < pRepro) {
                    T[] chs = ch1.reproduce(ch2, getContext(), pMutate);
                    
                    newCh1  = chs[0];
                    newCh2  = chs[1];
                    
                    newCh1.evaluate(getContext());
                    newCh2.evaluate(getContext());
                    
                    best    = getBetter(best, newCh1);
                    best    = getBetter(best, newCh2);
                    
                    
                } else {
                    newCh1  = ch1;
                    newCh2  = ch2;
                }
                
                newPopulation.add(newCh1);
                newPopulation.add(newCh2);
            }
            
            population  = newPopulation;
            ++time;
        }
        
        return best;
    }

    private T getBest(ArrayList<T> population) {
        if (population == null) throw new IllegalArgumentException("population cannot be null");
        if (population.size() < 1) throw new IllegalArgumentException("population must contain at least one element");
        
        T best  = null;
        
        for (T chromosome : population) {
            
            if (best == null || chromosome.isBetterThan(best)) {
                best    = chromosome;
            }
        }
        
        assert best != null;
        
        return best;
    }
    
    public T getBetter(T a, T b) {       
        if (a.isBetterThan(b)) {
            return a;
        } else {
            return b;
        }
    }    
}